/**
 * Copyright (c) 2009 blurredVision
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
 * associated documentation files (the "Software"), to deal in the Software without restriction, 
 * including without limitation the rights to use, copy, modify, merge, publish, distribute, 
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or 
 * substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 
 * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 
 * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
#include "StdAfx.h"
#include "IEmitter.h"
#include "ResourceFactory.h"
#include "Timer.h"
#include "IScene.h"

#ifdef CORE_DIRECTX
#include "dx/DXEmitter.h"
#endif

#ifdef _OPENMP
//#include <omp.h>
#endif

namespace particlesystem {

int IEmitter::s_iNumEmitter = 0;
bool IEmitter::s_bLimitEmitter = true;
std::map<math::Vector3, IEmitter*> IEmitter::s_lEmitter;

math::Vector3 IEmitter::vGravity = math::Vector3(0,-9.8f,0);

IEmitter::IEmitter(void)
:	m_iMaxParticles(0),
	m_pParticles(NULL),
	m_dDT(0),
	m_bStarted(false),
	m_bPaused(false),
	m_bFinalized(false),
	m_dPartMaxLifetime(0),
	m_dStartTime(0),
	m_dGenDuration(0),
	m_fPartMinSize(1),
	m_fPartMaxSize(100),
	m_fPartSizeVar(0),
	m_fPartWeight(0),
	m_vPartInitColor(.4f, .4f, .4f, 1.0f),
	m_vOffset(0,0,0),
	m_dTimer(-1),
	viewportheight(coreGetMainWindow()->getHeight()),
	m_bNeedsSorting(false),
	m_iNumAlive(0),
	m_pSpawnTrans(NULL),
	m_pEffect(NULL),
	m_pTexture(NULL),
	m_fVariance(0),
	m_fHalfVar(0),
	m_dGenerationDelta(0.1),
	hTexHandle(0),
	hViewPortHeight(0)
{
	++s_iNumEmitter;

	m_pTransform = new StaticTransform();
	m_pSpawnTrans = m_pTransform;
}

IEmitter::~IEmitter(void)
{
 	DELETE_ARRAY(m_pParticles);
	m_iMaxParticles = 0;
	// Delete the transform
	DELETE_POINTER(m_pTransform);
	--s_iNumEmitter;
	for (std::map<math::Vector3, IEmitter*>::iterator it = s_lEmitter.begin(); it != s_lEmitter.end(); ++it) {
		if (it->second == this) {
			it = s_lEmitter.erase(it);
			if (it == s_lEmitter.end())
				return;
		}
	}
}

IEmitter* IEmitter::Create(const math::Vector3 &position)
{
	IEmitter* resource = NULL;
	if (s_bLimitEmitter) {
		// Round the position to approx a dec.point
		math::Vector3 pos((int)position.x*10, (int)position.y*10, (int)position.y*10);

		if (s_lEmitter.find(pos) == s_lEmitter.end()) {
#ifdef CORE_DIRECTX
			resource = new directx::DXEmitter();
#endif
			s_lEmitter[pos] = resource;
		}
	} else {
#ifdef CORE_DIRECTX
		resource = new directx::DXEmitter();
#endif
	}
	// Set the position
	if( resource) {
		resource->getTransform()->setPosition( position);
	}

	return resource;
}

bool IEmitter::init(unsigned int maxParticles, double maxLife, double genDelta, const math::Vector3 &initVel, float variance)
{
	// Release old resources
	release();

	// Delete an old particle array
 	DELETE_ARRAY(m_pParticles);

	m_iMaxParticles = maxParticles;
	m_dGenerationDelta = genDelta;
	m_vInitialVelocity = initVel;
	m_dPartMaxLifetime = maxLife;
	m_fVariance = variance;
	m_fHalfVar = m_fVariance / 2.0f;

	// Allocate memory for all the particles
	m_pParticles = new Particle[m_iMaxParticles];

	reset();
	return true;
}

bool IEmitter::load(const std::string &shader, const std::string &texture)
{
	// Release old resources
	release();

	// Delete an old particle array
 	DELETE_ARRAY(m_pParticles);

	m_pEffect = coreGetResourceFactory()->loadShaderEffect( shader );
	if (!m_pEffect) 
		return false;

	m_pTexture = coreGetResourceFactory()->loadTexture( texture );
	if (!m_pTexture) 
		return false;

	if (m_pEffect->hasParameter("MaxParticles")) 
		m_iMaxParticles    = *(m_pEffect->getParameterValueAsIntArray("MaxParticles", 1));

	if (m_pEffect->hasParameter("GenerationDelta")) 
		m_dGenerationDelta = *(m_pEffect->getParameterValueAsFloatArray("GenerationDelta", 1));

	if (m_pEffect->hasParameter("InitialVelocity")) 
		m_vInitialVelocity = math::Vector3(m_pEffect->getParameterValueAsFloatArray("InitialVelocity", 3));

	if (m_pEffect->hasParameter("VelocityVariance")) 
		m_fVariance        = *(m_pEffect->getParameterValueAsFloatArray("VelocityVariance", 1));
	m_fHalfVar = m_fVariance / 2.0f;

	if (m_pEffect->hasParameter("GenerationDuration")) 
		m_dGenDuration     = *(m_pEffect->getParameterValueAsFloatArray("GenerationDuration", 1));

	if (m_pEffect->hasParameter("ParticleLifetime")) 
		m_dPartMaxLifetime = *(m_pEffect->getParameterValueAsFloatArray("ParticleLifetime", 1));

	if (m_pEffect->hasParameter("ParticleMinSize")) 
		m_fPartMinSize     = *(m_pEffect->getParameterValueAsFloatArray("ParticleMinSize", 1));

	if (m_pEffect->hasParameter("ParticleMaxSize")) 
		m_fPartMaxSize     = *(m_pEffect->getParameterValueAsFloatArray("ParticleMaxSize", 1));

	if (m_pEffect->hasParameter("ParticleSizeVariance")) 
		m_fPartSizeVar     = *(m_pEffect->getParameterValueAsFloatArray("ParticleSizeVariance", 1));

	if (m_pEffect->hasParameter("ParticleWeight")) 
		m_fPartWeight      = *(m_pEffect->getParameterValueAsFloatArray("ParticleWeight", 1));
	
	if (m_pEffect->hasParameter("ParticleColor")) {
		m_vPartInitColor   = math::Vector4(m_pEffect->getParameterValueAsFloatArray("ParticleColor", 4));
		if (!m_pEffect->hasParameter("ParticleFadeColor")) {
			m_vPartFadeColor = math::Vector4(m_vPartInitColor.x, m_vPartInitColor.y, m_vPartInitColor.z, 0);			
		}
	}
	if (m_pEffect->hasParameter("NeedsSorting")) {
		m_bNeedsSorting = static_cast<bool>(*m_pEffect->getParameterValueAsIntArray("NeedsSorting", 1));
	}
	if (m_pEffect->hasParameter("ParticleFadeColor")) 
		m_vPartFadeColor   = math::Vector4(m_pEffect->getParameterValueAsFloatArray("ParticleFadeColor", 4));

	hTexHandle = m_pEffect->getParameterByName("tex");
	hViewPortHeight = m_pEffect->getParameterByName("viewPortHeight");

	// Allocate memory for all the particles
	m_pParticles = new Particle[m_iMaxParticles];

	reset();

	return true;
}

void IEmitter::setTransform(ITransformable *trans)
{
	m_pSpawnTrans = trans;
	m_pSpawnTrans->addRef();
}

void IEmitter::updateVisiblity(Frustum *frustum)
{
	static model::BoundingSphere transformedSphere;

	if (pParent == NULL)
		s_uiVisibleObjects = 0;

	if (!m_bLockVisibility) {
		// Funktion, um mit Hilfe der beinhalteten BoundingSphere zu pruefen, ob sich die unterliegenden
		// Meshes dieses Knotens innerhalb des Frustums befinden
		for(child_list::const_iterator  it =lChildren.begin(); it != lChildren.end(); ++it ) {
			(*it)->updateVisiblity(frustum);
		}

		for(child_list::const_iterator  it =lTranspChildren.begin(); it != lTranspChildren.end(); ++it ) {
			(*it)->updateVisiblity(frustum);
		}

		if (m_bStarted)
			transformedSphere.setSize( m_bbSphere.getCenter() * m_pSpawnTrans->getMatrix(), m_bbSphere.getRadius() );

		m_bVisible = false;
		// if this bounding sphere is inside the frustum, traverse the children
		if ( frustum->sphereInFrustum( transformedSphere ) ) {
			m_bVisible = true;
			++s_uiVisibleObjects;
		} 
	}
}

void IEmitter::update(double dt)
{
	static int i;
	bool onealive = false;
	static math::Vector3 camPos;
	static float ft;
	ft = static_cast<float>(dt);

	m_dDT += dt;

	if (m_dTimer != -1) {
		m_dTimer -= dt;
		if (m_dTimer <= 0) 
			m_bStarted = false;
	}

	camPos = coreGetRenderer()->getCamera()->getPosition();
	
	// Creation phase
	for (i=0; i<m_iMaxParticles; ++i) {
		// Generate new particles
		if (m_bStarted && m_pParticles[i].getState() == PARTICLE_DEAD && m_dDT >= m_dGenerationDelta) {
			if (!m_bPaused)
				m_pParticles[i].create(this);
			
			m_dDT -= m_dGenerationDelta;
		}
	}

	// Update phase
	#pragma omp parallel private (i) shared(onealive)
	{
		#pragma omp for
		for (i=0; i<m_iMaxParticles; ++i) {
			// Update existing particles
			if (m_pParticles[i].update(ft)) {
				if (m_bNeedsSorting) {
					m_pParticles[i].m_fCamDist = m_pParticles[i].m_sData.vPosition.sqrDistance(camPos);
				}
				onealive = true;
			} else {
				m_pParticles[i].m_fCamDist = -1;
			}
		}
	}

	if (m_dGenDuration > 0) {
		if (m_dStartTime > 0 && m_dStartTime + m_dGenDuration < coreGetTimer()->getTime()) {
			stop();
		}
	}
	if (!m_bStarted && !onealive && !m_bPaused) {
		m_bFinalized = true;
	}

	if (m_bFinalized) {
		m_bDelete = true;
	}

	m_bDataUpdate = true;
}

void IEmitter::draw()
{
	coreGetRenderer()->pushMatrix();
	if (m_bNeedsSorting) {
		// TODO: Sort particles.
	}

	// Update the particle stream data
	if (m_bDataUpdate)
		updateData();

	viewportheight = coreGetRenderer()->getBackBufferSize().y;

	if (m_pEffect->bind()) {
		// Set uniform variables
		m_pEffect->setTexture(hTexHandle, m_pTexture);
		m_pEffect->setInt(hViewPortHeight, &viewportheight, 1);
		// Draw the passes
		for (int i=0; i<m_pEffect->getNumPasses(); ++i) {
			if (m_pEffect->beginPass(i)) {
				m_pEffect->setVariables();
				// Draw the particle buffer
				drawParticles();
				m_pEffect->endPass();
			}
		}
		m_pEffect->unbind();
	}
	coreGetRenderer()->popMatrix();
}

void IEmitter::start()
{
	m_bStarted = true;	
	m_bFinalized = false;
	if (coreGetTimer())
		m_dStartTime = coreGetTimer()->getTime();
}

void IEmitter::stop(double timer)
{
 	m_dTimer = timer;
}

void IEmitter::pause(bool value)
{
	m_bPaused = value;
}

void IEmitter::setMaxLifetime(double maxlife)
{
	m_dPartMaxLifetime = maxlife;
}

void IEmitter::setGenerationDelta(double gendelta)
{
	m_dGenerationDelta = gendelta;
}

void IEmitter::setInitialVelocity(const math::Vector3 &initVel, float variance)
{
	m_vInitialVelocity = initVel;
	m_fVariance = variance;
	m_fHalfVar = m_fVariance / 2.0f;
}

const bool IEmitter::isFinalized() const
{
	return m_bFinalized;
}

void IEmitter::calculateBoundingSphere()
{
	m_bbSphere.setRadius(100);
}

void IEmitter::setOffset(const math::Vector3 offset)
{
	m_vOffset = offset;
}

const math::Vector3& IEmitter::getOffset() const
{
	return m_vOffset;
}

void IEmitter::setInitialColor(const math::Vector4 &color)
{
	m_vPartInitColor = color;
}

const float IEmitter::getVariance() const
{
	return m_fVariance;
}

void IEmitter::setNormal(const math::Vector3 &normal)
{
	m_vNormal = normal;
	SHADERHANDLE n = m_pEffect->getParameterByName("normal");
	if (n != 0){
		m_pEffect->setFloat(n, &m_vNormal.x, 3);
	}
}

int IEmitter::GetNumEmitter()
{
	return s_iNumEmitter;
}

void IEmitter::LimitEmitter(bool val)
{
	s_bLimitEmitter = val;
}

} // namespace
